<?php

/**
 * This file is part of PHPWord - A pure PHP library for reading and writing
 * word processing documents.
 *
 * PHPWord is free software distributed under the terms of the GNU Lesser
 * General Public License version 3 as published by the Free Software Foundation.
 *
 * For the full copyright and license information, please read the LICENSE
 * file that was distributed with this source code. For the full list of
 * contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
 *
 * @see         https://github.com/PHPOffice/PHPWord
 *
 * @license     http://www.gnu.org/licenses/lgpl.txt LGPL version 3
 */

namespace PhpOffice\PhpWordTests\Shared;

use Exception;
use PhpOffice\PhpWord\ComplexType\RubyProperties;
use PhpOffice\PhpWord\Element\Section;
use PhpOffice\PhpWord\Element\Table;
use PhpOffice\PhpWord\Element\Text;
use PhpOffice\PhpWord\Element\TextRun;
use PhpOffice\PhpWord\PhpWord;
use PhpOffice\PhpWord\Shared\Converter;
use PhpOffice\PhpWord\Shared\Html;
use PhpOffice\PhpWord\SimpleType\Jc;
use PhpOffice\PhpWord\SimpleType\LineSpacingRule;
use PhpOffice\PhpWord\SimpleType\TblWidth;
use PhpOffice\PhpWord\Style\Font;
use PhpOffice\PhpWord\Style\Paragraph;
use PhpOffice\PhpWordTests\AbstractWebServerEmbedded;
use PhpOffice\PhpWordTests\TestHelperDOCX;

/**
 * Test class for PhpOffice\PhpWord\Shared\Html.
 *
 * @coversDefaultClass \PhpOffice\PhpWord\Shared\Html
 */
class HtmlTest extends AbstractWebServerEmbedded
{
    /**
     * Tear down after each test.
     */
    protected function tearDown(): void
    {
        TestHelperDOCX::clear();
    }

    /**
     * Test unit conversion functions with various numbers.
     */
    public function testAddHtml(): void
    {
        $content = '';

        // Default
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        self::assertCount(0, $section->getElements());

        // Heading
        $styles = ['strong', 'em', 'sup', 'sub'];
        for ($level = 1; $level <= 6; ++$level) {
            $content .= "<h{$level}>Heading {$level}</h{$level}>";
        }

        // Styles
        $content .= '<p style="text-decoration: underline; text-decoration: line-through; '
                  . 'text-align: center; color: #999; background-color: #000; font-weight: bold; font-style: italic;">';
        foreach ($styles as $style) {
            $content .= "<{$style}>{$style}</{$style}>";
        }
        $content .= '</p>';

        // Add HTML
        Html::addHtml($section, $content);
        self::assertCount(7, $section->getElements());

        // Other parts
        $section = $phpWord->addSection();
        $content = '';
        $content .= '<table><tr><th>Header</th><td>Content</td></tr></table>';
        $content .= '<ul><li>Bullet</li><ul><li>Bullet</li></ul></ul>';
        $content .= '<ol><li>Bullet</li></ol>';
        $content .= "'Single Quoted Text'";
        $content .= '"Double Quoted Text"';
        $content .= '& Ampersand';
        $content .= '&lt;&gt;&ldquo;&lsquo;&rsquo;&laquo;&raquo;&lsaquo;&rsaquo;';
        $content .= '&amp;&bull;&deg;&hellip;&trade;&copy;&reg;&mdash;';
        $content .= '&ndash;&nbsp;&emsp;&ensp;&sup2;&sup3;&frac14;&frac12;&frac34;';
        Html::addHtml($section, $content);
    }

    /**
     * Test that html already in body element can be read.
     *
     * @ignore
     */
    public function testParseFullHtml(): void
    {
        $section = new Section(1);
        Html::addHtml($section, '<body><p>test paragraph1</p><p>test paragraph2</p></body>', true);

        self::assertCount(2, $section->getElements());
    }

    public function testParseHeader(): void
    {
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        Html::addHtml($section, '<h1>Text</h1>');

        self::assertCount(1, $section->getElements());
        $element = $section->getElement(0);
        self::assertInstanceOf(TextRun::class, $element);
        self::assertInstanceOf(Paragraph::class, $element->getParagraphStyle());
        self::assertEquals('Heading1', $element->getParagraphStyle()->getStyleName());
        self::assertEquals('', $element->getParagraphStyle()->getAlignment());
        self::assertEquals('Text', $element->getText());
        self::assertCount(1, $element->getElements());
        $subElement = $element->getElement(0);
        self::assertInstanceOf(Text::class, $subElement);
        self::assertInstanceOf(Font::class, $subElement->getFontStyle());
        self::assertNull($subElement->getFontStyle()->getColor());
        self::assertEquals('Text', $subElement->getText());
    }

    public function testParseHeaderStyle(): void
    {
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        Html::addHtml($section, '<h1 style="color: #ff0000; text-align:center">Text</h1>');

        self::assertCount(1, $section->getElements());
        $element = $section->getElement(0);
        self::assertInstanceOf(TextRun::class, $element);
        self::assertInstanceOf(Paragraph::class, $element->getParagraphStyle());
        self::assertEquals('Heading1', $element->getParagraphStyle()->getStyleName());
        self::assertEquals('center', $element->getParagraphStyle()->getAlignment());
        self::assertEquals('Text', $element->getText());
        self::assertCount(1, $element->getElements());
        $subElement = $element->getElement(0);
        self::assertInstanceOf(Text::class, $subElement);
        self::assertInstanceOf(Font::class, $subElement->getFontStyle());
        self::assertEquals('ff0000', $subElement->getFontStyle()->getColor());
        self::assertEquals('Text', $subElement->getText());
    }

    /**
     * Test HTML entities.
     */
    public function testParseHtmlEntities(): void
    {
        \PhpOffice\PhpWord\Settings::setOutputEscapingEnabled(true);
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        Html::addHtml($section, 'text with entities &lt;my text&gt;');

        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');
        self::assertTrue($doc->elementExists('/w:document/w:body/w:p[1]/w:r/w:t'));
        self::assertEquals('text with entities <my text>', $doc->getElement('/w:document/w:body/w:p[1]/w:r/w:t')->nodeValue);
    }

    public function testParseStyle(): void
    {
        $html = '<style type="text/css">
        .pStyle {
          font-size:15px;
        }
        .tableStyle {
          width:100%;
          background-color:red;
        }
        </style>
        
        <p class="pStyle">Calculator</p>';
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        Html::addHtml($section, $html);

        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');
        self::assertTrue($doc->elementExists('/w:document/w:body/w:p[2]'));
        self::assertTrue($doc->elementExists('/w:document/w:body/w:p[2]/w:r'));
        self::assertTrue($doc->elementExists('/w:document/w:body/w:p[2]/w:r/w:t'));
        self::assertEquals('Calculator', $doc->getElement('/w:document/w:body/w:p[2]/w:r/w:t')->nodeValue);
        self::assertTrue($doc->elementExists('/w:document/w:body/w:p[2]/w:r/w:rPr'));
        self::assertTrue($doc->elementExists('/w:document/w:body/w:p[2]/w:r/w:rPr/w:sz'));
        self::assertEquals('22.5', $doc->getElementAttribute('/w:document/w:body/w:p[2]/w:r/w:rPr/w:sz', 'w:val'));
    }

    public function testParseStyleTableClassName(): void
    {
        $html = '<style type="text/css">.pStyle { font-size:15px; }</style><table class="pStyle"><tr><td></td></tr></table>';
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        Html::addHtml($section, $html);

        self::assertInstanceOf(Table::class, $section->getElement(0));
        self::assertEquals('pStyle', $section->getElement(0)->getStyle()->getStyleName());
    }

    /**
     * Test text-decoration style.
     */
    public function testParseTextDecoration(): void
    {
        $html = '<span style="text-decoration: underline;">test</span>';
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        Html::addHtml($section, $html);

        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');
        self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:rPr/w:u'));
        self::assertEquals('single', $doc->getElementAttribute('/w:document/w:body/w:p/w:r/w:rPr/w:u', 'w:val'));
    }

    /**
     * Test underline.
     */
    public function testParseUnderline(): void
    {
        $html = '<u>test</u>';
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        Html::addHtml($section, $html);

        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');
        self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:rPr/w:u'));
        self::assertEquals('single', $doc->getElementAttribute('/w:document/w:body/w:p/w:r/w:rPr/w:u', 'w:val'));
    }

    /**
     * Test width.
     *
     * @dataProvider providerParseWidth
     */
    public function testParseWidth(string $htmlSize, float $docxSize, string $docxUnit): void
    {
        $html = '<table width="' . $htmlSize . '"><tr><td>A</td></tr></table>';
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();

        Html::addHtml($section, $html);
        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');
        $xpath = '/w:document/w:body/w:tbl/w:tblPr/w:tblW';
        self::assertTrue($doc->elementExists($xpath));
        self::assertEquals($docxSize, $doc->getElement($xpath)->getAttribute('w:w'));
        self::assertEquals($docxUnit, $doc->getElement($xpath)->getAttribute('w:type'));
    }

    /**
     * Test font-variant style.
     */
    public function testParseFontVariant(): void
    {
        $html = '<span style="font-variant: small-caps;">test</span>';
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        Html::addHtml($section, $html);

        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');
        self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:rPr/w:smallCaps'));
        self::assertEquals('1', $doc->getElementAttribute('/w:document/w:body/w:p/w:r/w:rPr/w:smallCaps', 'w:val'));
    }

    /**
     * Test font.
     */
    public function testParseFont(): void
    {
        $html = '<font style="font-family: Arial;">test</font>';
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        Html::addHtml($section, $html);

        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');
        self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:rPr'));
        //TODO check style
    }

    /**
     * Test line-height style.
     */
    public function testParseLineHeight(): void
    {
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        Html::addHtml($section, '<p style="line-height: 1.5;">test</p>');
        Html::addHtml($section, '<p style="line-height: 15pt;">test</p>');
        Html::addHtml($section, '<p style="line-height: 120%;">test</p>');
        Html::addHtml($section, '<p style="line-height: 0.17in;">test</p>');
        Html::addHtml($section, '<p style="line-height: normal;">test</p>');
        Html::addHtml($section, '<p style="line-height: inherit;">test</p>');

        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');
        self::assertTrue($doc->elementExists('/w:document/w:body/w:p[1]/w:pPr/w:spacing'));
        self::assertEquals(Paragraph::LINE_HEIGHT * 1.5, $doc->getElementAttribute('/w:document/w:body/w:p[1]/w:pPr/w:spacing', 'w:line'));
        self::assertEquals(LineSpacingRule::AUTO, $doc->getElementAttribute('/w:document/w:body/w:p[1]/w:pPr/w:spacing', 'w:lineRule'));

        self::assertTrue($doc->elementExists('/w:document/w:body/w:p[2]/w:pPr/w:spacing'));
        self::assertEquals(300, $doc->getElementAttribute('/w:document/w:body/w:p[2]/w:pPr/w:spacing', 'w:line'));
        self::assertEquals(LineSpacingRule::EXACT, $doc->getElementAttribute('/w:document/w:body/w:p[2]/w:pPr/w:spacing', 'w:lineRule'));

        self::assertTrue($doc->elementExists('/w:document/w:body/w:p[3]/w:pPr/w:spacing'));
        self::assertEquals(Paragraph::LINE_HEIGHT * 1.2, $doc->getElementAttribute('/w:document/w:body/w:p[3]/w:pPr/w:spacing', 'w:line'));
        self::assertEquals(LineSpacingRule::AUTO, $doc->getElementAttribute('/w:document/w:body/w:p[3]/w:pPr/w:spacing', 'w:lineRule'));

        self::assertTrue($doc->elementExists('/w:document/w:body/w:p[4]/w:pPr/w:spacing'));
        self::assertEquals(244.8, $doc->getElementAttribute('/w:document/w:body/w:p[4]/w:pPr/w:spacing', 'w:line'));
        self::assertEquals(LineSpacingRule::EXACT, $doc->getElementAttribute('/w:document/w:body/w:p[4]/w:pPr/w:spacing', 'w:lineRule'));

        self::assertTrue($doc->elementExists('/w:document/w:body/w:p[5]/w:pPr/w:spacing'));
        self::assertEquals(Paragraph::LINE_HEIGHT, $doc->getElementAttribute('/w:document/w:body/w:p[5]/w:pPr/w:spacing', 'w:line'));
        self::assertEquals(LineSpacingRule::AUTO, $doc->getElementAttribute('/w:document/w:body/w:p[5]/w:pPr/w:spacing', 'w:lineRule'));

        self::assertTrue($doc->elementExists('/w:document/w:body/w:p[6]/w:pPr/w:spacing'));
        self::assertEquals(Paragraph::LINE_HEIGHT, $doc->getElementAttribute('/w:document/w:body/w:p[6]/w:pPr/w:spacing', 'w:line'));
        self::assertEquals(LineSpacingRule::AUTO, $doc->getElementAttribute('/w:document/w:body/w:p[6]/w:pPr/w:spacing', 'w:lineRule'));
    }

    public function testParseCellPaddingStyle(): void
    {
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();

        $top = 10;
        $right = 11;
        $bottom = 12;
        $left = 13;

        $testValTop = Converter::pixelToTwip($top);
        $testValRight = Converter::pixelToTwip($right);
        $testValBottom = Converter::pixelToTwip($bottom);
        $testValLeft = Converter::pixelToTwip($left);

        $html = '<table>
            <tbody>
                <tr>
                    <td style="padding:' . $top . 'px ' . $right . 'px ' . $bottom . 'px ' . $left . 'px;">full</td>
                    <td style="padding:' . $top . 'px 0px ' . $bottom . 'px ' . $left . 'px;padding-right:' . $right . 'px;">mix</td>
                    <td style="padding-top:' . $top . 'px;">top</td>
                    <td style="padding-bottom:' . $bottom . 'px;">bottom</td>
                    <td style="padding-left:' . $left . 'px;">left</td>
                </tr>
            </tbody>
        </table>';
        Html::addHtml($section, $html);

        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');

        $path = '/w:document/w:body/w:tbl/w:tr/w:tc[1]/w:tcPr/w:tcMar/w:top';
        self::assertTrue($doc->elementExists($path));
        $path = '/w:document/w:body/w:tbl/w:tr/w:tc[1]/w:tcPr/w:tcMar/w:bottom';
        self::assertTrue($doc->elementExists($path));
        $path = '/w:document/w:body/w:tbl/w:tr/w:tc[1]/w:tcPr/w:tcMar/w:end';
        self::assertTrue($doc->elementExists($path));
        $path = '/w:document/w:body/w:tbl/w:tr/w:tc[1]/w:tcPr/w:tcMar/w:start';
        self::assertTrue($doc->elementExists($path));

        $path = '/w:document/w:body/w:tbl/w:tr/w:tc[2]/w:tcPr/w:tcMar/w:end';
        self::assertTrue($doc->elementExists($path));
        self::assertEquals($testValRight, $doc->getElementAttribute($path, 'w:w'));
        self::assertEquals(TblWidth::TWIP, $doc->getElementAttribute($path, 'w:type'));

        $path = '/w:document/w:body/w:tbl/w:tr/w:tc[3]/w:tcPr/w:tcMar/w:top';
        self::assertTrue($doc->elementExists($path));
        self::assertEquals($testValTop, $doc->getElementAttribute($path, 'w:w'));
        self::assertEquals(TblWidth::TWIP, $doc->getElementAttribute($path, 'w:type'));

        $path = '/w:document/w:body/w:tbl/w:tr/w:tc[4]/w:tcPr/w:tcMar/w:bottom';
        self::assertTrue($doc->elementExists($path));
        self::assertEquals($testValBottom, $doc->getElementAttribute($path, 'w:w'));
        self::assertEquals(TblWidth::TWIP, $doc->getElementAttribute($path, 'w:type'));

        $path = '/w:document/w:body/w:tbl/w:tr/w:tc[5]/w:tcPr/w:tcMar/w:start';
        self::assertTrue($doc->elementExists($path));
        self::assertEquals($testValLeft, $doc->getElementAttribute($path, 'w:w'));
        self::assertEquals(TblWidth::TWIP, $doc->getElementAttribute($path, 'w:type'));
    }

    /**
     * Test text-indent style.
     */
    public function testParseTextIndent(): void
    {
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        Html::addHtml($section, '<p style="text-indent: 50px;">test</p>');

        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');
        self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:pPr/w:ind'));
        self::assertEquals(750, $doc->getElementAttribute('/w:document/w:body/w:p/w:pPr/w:ind', 'w:firstLine'));
    }

    /**
     * Test text-align style.
     */
    public function testParseTextAlign(): void
    {
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        Html::addHtml($section, '<p style="text-align: left;">test</p>');
        Html::addHtml($section, '<p style="text-align: right;">test</p>');
        Html::addHtml($section, '<p style="text-align: center;">test</p>');
        Html::addHtml($section, '<p style="text-align: justify;">test</p>');

        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');
        self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:pPr/w:jc'));
        self::assertEquals(Jc::START, $doc->getElementAttribute('/w:document/w:body/w:p[1]/w:pPr/w:jc', 'w:val'));
        self::assertEquals(Jc::END, $doc->getElementAttribute('/w:document/w:body/w:p[2]/w:pPr/w:jc', 'w:val'));
        self::assertEquals(Jc::CENTER, $doc->getElementAttribute('/w:document/w:body/w:p[3]/w:pPr/w:jc', 'w:val'));
        self::assertEquals(Jc::BOTH, $doc->getElementAttribute('/w:document/w:body/w:p[4]/w:pPr/w:jc', 'w:val'));
    }

    /**
     * Test font-size style.
     */
    public function testParseFontSize(): void
    {
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        Html::addHtml($section, '<span style="font-size: 10pt;">test</span>');
        Html::addHtml($section, '<span style="font-size: 10px;">test</span>');

        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');
        self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:rPr/w:sz'));
        self::assertEquals('20', $doc->getElementAttribute('/w:document/w:body/w:p[1]/w:r/w:rPr/w:sz', 'w:val'));
        self::assertEquals('15', $doc->getElementAttribute('/w:document/w:body/w:p[2]/w:r/w:rPr/w:sz', 'w:val'));
    }

    /**
     * Test direction style.
     */
    public function testParseTextDirection(): void
    {
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        Html::addHtml($section, '<span style="direction: rtl">test</span>');

        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');
        self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:rPr/w:rtl'));
    }

    /**
     * Test html lang.
     */
    public function testParseLang(): void
    {
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        Html::addHtml($section, '<span lang="fr-BE">test</span>');

        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');
        self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:rPr/w:lang'));
        self::assertEquals('fr-BE', $doc->getElementAttribute('/w:document/w:body/w:p/w:r/w:rPr/w:lang', 'w:val'));
    }

    /**
     * Test font-family style.
     */
    public function testParseFontFamily(): void
    {
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        Html::addHtml($section, '<span style="font-family: Arial">test</span>');
        Html::addHtml($section, '<span style="font-family: Times New Roman;">test</span>');

        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');
        self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:rPr/w:rFonts'));
        self::assertEquals('Arial', $doc->getElementAttribute('/w:document/w:body/w:p[1]/w:r/w:rPr/w:rFonts', 'w:ascii'));
        self::assertEquals('Times New Roman', $doc->getElementAttribute('/w:document/w:body/w:p[2]/w:r/w:rPr/w:rFonts', 'w:ascii'));
    }

    /**
     * Test parsing paragraph and span styles.
     */
    public function testParseParagraphAndSpanStyle(): void
    {
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        Html::addHtml($section, '<p style="text-align: center; margin-top: 15px; margin-bottom: 15px;"><span style="text-decoration: underline;">test</span></p>');

        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');
        self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:pPr/w:jc'));
        self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:pPr/w:spacing'));
        self::assertEquals(Jc::CENTER, $doc->getElementAttribute('/w:document/w:body/w:p[1]/w:pPr/w:jc', 'w:val'));
        self::assertEquals('single', $doc->getElementAttribute('/w:document/w:body/w:p[1]/w:r/w:rPr/w:u', 'w:val'));
    }

    /**
     * Test parsing paragraph with `page-break-after` style.
     */
    public function testParseParagraphWithPageBreak(): void
    {
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        Html::addHtml($section, '<p style="page-break-after:always;"></p>');

        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');
        self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:br'));
        self::assertEquals('page', $doc->getElementAttribute('/w:document/w:body/w:p/w:r/w:br', 'w:type'));
    }

    /**
     * Test parsing table.
     */
    public function testParseTable(): void
    {
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        $html = '<table align="left" style="width: 50%; border: 12px #0000FF double">
                <thead>
                    <tr style="background-color: #FF0000; text-align: center; color: #FFFFFF; font-weight: bold">
                        <th style="width: 50pt"><p>header a</p></th>
                        <th style="width: 50; border-color: #00EE00; border-width: 3px"><span>header b</span></th>
                        <th style="border-color: #00AA00 #00BB00 #00CC00 #00DD00; border-width: 3px">header c</th>
                    </tr>
                </thead>
                <tbody>
                    <tr><td style="border-style: dotted;">1</td><td colspan="2">2</td></tr>
                    <tr><td>This is <b>bold</b> text</td><td>5</td><td><p>6</p></td></tr>
                </tbody>
            </table>';
        Html::addHtml($section, $html);

        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');
        self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl'));
        self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr/w:tc'));
        self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tblPr/w:jc'));
        self::assertEquals(Jc::START, $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tblPr/w:jc', 'w:val'));

        //check border colors
        self::assertEquals('00EE00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[2]/w:tcPr/w:tcBorders/w:top', 'w:color'));
        self::assertEquals('00EE00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[2]/w:tcPr/w:tcBorders/w:right', 'w:color'));
        self::assertEquals('00EE00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[2]/w:tcPr/w:tcBorders/w:bottom', 'w:color'));
        self::assertEquals('00EE00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[2]/w:tcPr/w:tcBorders/w:left', 'w:color'));

        self::assertEquals('00AA00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[3]/w:tcPr/w:tcBorders/w:top', 'w:color'));
        self::assertEquals('00BB00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[3]/w:tcPr/w:tcBorders/w:right', 'w:color'));
        self::assertEquals('00CC00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[3]/w:tcPr/w:tcBorders/w:bottom', 'w:color'));
        self::assertEquals('00DD00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[3]/w:tcPr/w:tcBorders/w:left', 'w:color'));

        //check borders are not propagated inside cells
        self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc[1]/w:p'));
        self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc[1]/w:p/w:pPr/w:pBdr'));
        self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc[2]/w:p'));
        self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc[2]/w:p/w:pPr/w:pBdr'));
    }

    /**
     * Parse widths in tables and cells, which also allows for controlling column width.
     */
    public function testParseTableAndCellWidth(): void
    {
        $phpWord = new PhpWord();
        $section = $phpWord->addSection([
            'orientation' => \PhpOffice\PhpWord\Style\Section::ORIENTATION_LANDSCAPE,
        ]);

        // borders & backgrounds are here just for better visual comparison
        $html = <<<HTML
<table style="border: 1px #000000 solid; width: 100%;">
    <tr>
        <td style="width: 25%; border: 1px #000000 solid; text-align: center;">25%</td>
        <td>
            <table width="400" style="border: 1px #000000 solid; background-color: #EEEEEE;">
                <tr>
                    <th colspan="3" style="border: 1px #000000 solid;">400px</th>
                </tr>
                <tr>
                    <th>T2.R2.C1</th>
                    <th style="width: 50pt; border: 1px #FF0000 dashed; background-color: #FFFFFF">50pt</th>
                    <th>T2.R2.C3</th>
                </tr>
                <tr>
                    <th width="300" colspan="2" style="border: 1px #000000 solid;">300px</th>
                    <th style="border: 1px #000000 solid;">T2.R3.C3</th>
                </tr>
            </table>
        </td>
    </tr>
</table>
HTML;

        Html::addHtml($section, $html);
        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');

        // outer table grid
        $xpath = '/w:document/w:body/w:tbl/w:tblGrid/w:gridCol';
        self::assertTrue($doc->elementExists($xpath));
        self::assertEquals(25 * 50, $doc->getElement($xpath)->getAttribute('w:w'));
        self::assertEquals(TblWidth::TWIP, $doc->getElement($xpath)->getAttribute('w:type'));

        // <td style="width: 25%; ...
        $xpath = '/w:document/w:body/w:tbl/w:tr/w:tc/w:tcPr/w:tcW';
        self::assertTrue($doc->elementExists($xpath));
        self::assertEquals(25 * 50, $doc->getElement($xpath)->getAttribute('w:w'));
        self::assertEquals(TblWidth::PERCENT, $doc->getElement($xpath)->getAttribute('w:type'));

        // <table width="400" .. 400px = 6000 twips (400 / 96 * 1440)
        $xpath = '/w:document/w:body/w:tbl/w:tr/w:tc/w:tbl/w:tr/w:tc/w:tcPr/w:tcW';
        self::assertTrue($doc->elementExists($xpath));
        self::assertEquals(6000, $doc->getElement($xpath)->getAttribute('w:w'));
        self::assertEquals(TblWidth::TWIP, $doc->getElement($xpath)->getAttribute('w:type'));

        // <th style="width: 50pt; .. 50pt = 750 twips (50 / 72 * 1440)
        $xpath = '/w:document/w:body/w:tbl/w:tr/w:tc/w:tbl/w:tr[2]/w:tc[2]/w:tcPr/w:tcW';
        self::assertTrue($doc->elementExists($xpath));
        self::assertEquals(1000, $doc->getElement($xpath)->getAttribute('w:w'));
        self::assertEquals(TblWidth::TWIP, $doc->getElement($xpath)->getAttribute('w:type'));

        // <th width="300" .. 300px = 4500 twips (300 / 96 * 1440)
        $xpath = '/w:document/w:body/w:tbl/w:tr/w:tc/w:tbl/w:tr[3]/w:tc/w:tcPr/w:tcW';
        self::assertTrue($doc->elementExists($xpath));
        self::assertEquals(4500, $doc->getElement($xpath)->getAttribute('w:w'));
        self::assertEquals(TblWidth::TWIP, $doc->getElement($xpath)->getAttribute('w:type'));
    }

    /**
     * Parse heights in rows, which also allows for controlling column height.
     */
    public function testParseTableRowHeight(): void
    {
        $phpWord = new PhpWord();
        $section = $phpWord->addSection([
            'orientation' => \PhpOffice\PhpWord\Style\Section::ORIENTATION_LANDSCAPE,
        ]);

        $html = <<<HTML
<table>
    <tr style="height: 100px;">
        <td>100px</td>
    </tr>
    <tr style="height: 200pt;">
        <td>200pt</td>
    </tr>
    <tr>
        <td>
            <table>
                <tr style="height: 300px;">
                    <td>300px</td>
                </tr>
            </table>
        </td>
    </tr>
</table>
HTML;

        Html::addHtml($section, $html);
        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');

        // <tr style="height: 100; ... 100px = 1500 twips (100 / 96 * 1440)
        $xpath = '/w:document/w:body/w:tbl/w:tr/w:trPr/w:trHeight';
        self::assertTrue($doc->elementExists($xpath));
        self::assertEquals(1500, $doc->getElement($xpath)->getAttribute('w:val'));
        self::assertEquals('exact', $doc->getElement($xpath)->getAttribute('w:hRule'));

        // <tr style="height: 200pt; ... 200pt = 4000 twips (200 / 72 * 1440)
        $xpath = '/w:document/w:body/w:tbl/w:tr[2]/w:trPr/w:trHeight';
        self::assertTrue($doc->elementExists($xpath));
        self::assertEquals(4000, $doc->getElement($xpath)->getAttribute('w:val'));
        self::assertEquals('exact', $doc->getElement($xpath)->getAttribute('w:hRule'));

        // <tr style="width: 300; .. 300px = 4500 twips (300 / 72 * 1440)
        $xpath = '/w:document/w:body/w:tbl/w:tr[3]/w:tc/w:tbl/w:tr/w:trPr/w:trHeight';
        self::assertTrue($doc->elementExists($xpath));
        self::assertEquals(4500, $doc->getElement($xpath)->getAttribute('w:val'));
        self::assertEquals('exact', $doc->getElement($xpath)->getAttribute('w:hRule'));
    }

    /**
     * Test parsing table (attribute border).
     */
    public function testParseTableAttributeBorder(): void
    {
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        $html = '<table border="10">
                <thead>
                    <tr>
                        <th>Header</th>
                    </tr>
                </thead>
                <tbody>
                    <tr><td>Cell 1</td></tr>
                    <tr><td>Cell 2</td></tr>
                </tbody>
            </table>';
        Html::addHtml($section, $html);

        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');
        self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl'));
        self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tblPr'));
        self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tblPr/w:tblBorders'));
        self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tblPr/w:tblBorders/w:top'));
        // 10 pixels = 150 twips
        self::assertEquals(150, $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tblPr/w:tblBorders/w:top', 'w:sz'));
    }

    /**
     * Test parsing background color for table rows and table cellspacing.
     */
    public function testParseTableCellspacingRowBgColor(): void
    {
        $phpWord = new PhpWord();
        $section = $phpWord->addSection([
            'orientation' => \PhpOffice\PhpWord\Style\Section::ORIENTATION_LANDSCAPE,
        ]);

        // borders & backgrounds are here just for better visual comparison
        $html = <<<HTML
<table cellspacing="3" bgColor="lightgreen" width="50%" align="center">
    <tr>
        <td>A</td>
        <td>B</td>
    </tr>
    <tr bgcolor="#FF0000">
        <td>C</td>
        <td>D</td>
    </tr>
</table>
HTML;

        Html::addHtml($section, $html);
        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');

        $xpath = '/w:document/w:body/w:tbl/w:tblPr/w:tblCellSpacing';
        self::assertTrue($doc->elementExists($xpath));
        self::assertEquals(3 * 15, $doc->getElement($xpath)->getAttribute('w:w'));
        self::assertEquals(TblWidth::TWIP, $doc->getElement($xpath)->getAttribute('w:type'));

        $xpath = '/w:document/w:body/w:tbl/w:tr[1]/w:tc[1]/w:tcPr/w:shd';
        self::assertTrue($doc->elementExists($xpath));
        self::assertEquals('lightgreen', $doc->getElement($xpath)->getAttribute('w:fill'));

        $xpath = '/w:document/w:body/w:tbl/w:tr[2]/w:tc[1]/w:tcPr/w:shd';
        self::assertTrue($doc->elementExists($xpath));
        self::assertEquals('FF0000', $doc->getElement($xpath)->getAttribute('w:fill'));
    }

    /**
     * Test parsing background color for table rows and table cellspacing.
     */
    public function testParseTableStyleAttributeInlineStyle(): void
    {
        $phpWord = new PhpWord();
        $section = $phpWord->addSection([
            'orientation' => \PhpOffice\PhpWord\Style\Section::ORIENTATION_LANDSCAPE,
        ]);

        $html = '<table style="background-color:red;width:100%;" bgColor="lightgreen" width="50%">
            <tr>
                <td>A</td>
            </tr>
        </table>';

        Html::addHtml($section, $html);
        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');

        $xpath = '/w:document/w:body/w:tbl/w:tblPr/w:tblW';
        self::assertTrue($doc->elementExists($xpath));
        self::assertEquals(100 * 50, $doc->getElement($xpath)->getAttribute('w:w'));
        self::assertEquals(TblWidth::PERCENT, $doc->getElement($xpath)->getAttribute('w:type'));

        $xpath = '/w:document/w:body/w:tbl/w:tr[1]/w:tc[1]/w:tcPr/w:shd';
        self::assertTrue($doc->elementExists($xpath));
        self::assertEquals('red', $doc->getElement($xpath)->getAttribute('w:fill'));
    }

    /**
     * Tests parsing of ul/li.
     */
    public function testParseList(): void
    {
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        $html = '<ul>
                <li>
                    <span style="font-family: arial,helvetica,sans-serif;">
                        <span style="font-size: 12px;">list item1</span>
                    </span>
                </li>
                <li>
                    <span style="font-family: arial,helvetica,sans-serif;">
                        <span style="font-size: 10px; font-weight: bold;">list item2</span>
                    </span>
                </li>
            </ul>';
        Html::addHtml($section, $html, false, false);

        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');
        self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:pPr/w:numPr/w:numId'));
        self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:t'));
        self::assertEquals('list item1', $doc->getElement('/w:document/w:body/w:p[1]/w:r/w:t')->nodeValue);
        self::assertEquals('list item2', $doc->getElement('/w:document/w:body/w:p[2]/w:r/w:t')->nodeValue);
        self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:rPr/w:b'));
    }

    /**
     * Tests parsing of ul/li.
     */
    public function testOrderedListNumbering(): void
    {
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        $html = '<ol>
                <li>List 1 item 1</li>
                <li>List 1 item 2</li>
            </ol>
            <p>Some Text</p>
            <ol>
                <li>List 2 item 1</li>
                <li>List 2 item 2</li>
            </ol>';
        Html::addHtml($section, $html, false, false);

        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');

        self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:pPr/w:numPr/w:numId'));
        self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:t'));

        self::assertEquals('List 1 item 1', $doc->getElement('/w:document/w:body/w:p[1]/w:r/w:t')->nodeValue);
        self::assertEquals('List 2 item 1', $doc->getElement('/w:document/w:body/w:p[4]/w:r/w:t')->nodeValue);

        $firstListnumId = $doc->getElementAttribute('/w:document/w:body/w:p[1]/w:pPr/w:numPr/w:numId', 'w:val');
        $secondListnumId = $doc->getElementAttribute('/w:document/w:body/w:p[4]/w:pPr/w:numPr/w:numId', 'w:val');

        self::assertNotEquals($firstListnumId, $secondListnumId);
    }

    /**
     * Tests parsing of nested ul/li.
     */
    public function testOrderedNestedListNumbering(): void
    {
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        $html = '<ol>
                <li>List 1 item 1</li>
                <li>List 1 item 2</li>
            </ol>
            <p>Some Text</p>
            <ol>
                <li>List 2 item 1</li>
                <li>
                    <ol>
                        <li>sub list 1</li>
                        <li>sub list 2</li>
                    </ol>
                </li>
            </ol>';
        Html::addHtml($section, $html, false, false);

        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');

        self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:pPr/w:numPr/w:numId'));
        self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:t'));

        self::assertEquals('List 1 item 1', $doc->getElement('/w:document/w:body/w:p[1]/w:r/w:t')->nodeValue);
        self::assertEquals('List 2 item 1', $doc->getElement('/w:document/w:body/w:p[4]/w:r/w:t')->nodeValue);

        $firstListnumId = $doc->getElementAttribute('/w:document/w:body/w:p[1]/w:pPr/w:numPr/w:numId', 'w:val');
        $secondListnumId = $doc->getElementAttribute('/w:document/w:body/w:p[4]/w:pPr/w:numPr/w:numId', 'w:val');

        self::assertNotEquals($firstListnumId, $secondListnumId);
    }

    /**
     * Tests parsing of ul/li.
     */
    public function testParseListWithFormat(): void
    {
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        $html = preg_replace('/\s+/', ' ', '<ul>
                <li>Some text before
                    <span style="font-family: arial,helvetica,sans-serif;">
                        <span style="font-size: 12px;">list item1 <b>bold</b> with text after bold</span>
                    </span>
                    and some after
                </li>
                <li>
                    <span style="font-family: arial,helvetica,sans-serif;">
                        <span style="font-size: 12px;">list item2</span>
                    </span>
                </li>
            </ul>');
        Html::addHtml($section, $html, false, false);

        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');
        self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:pPr/w:numPr/w:numId'));
        self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:t'));
        self::assertEquals('list item2', $doc->getElement('/w:document/w:body/w:p[2]/w:r/w:t')->nodeValue);
        self::assertTrue($doc->elementExists('/w:document/w:body/w:p[1]/w:r[3]/w:rPr/w:b'));
        self::assertEquals('bold', $doc->getElement('/w:document/w:body/w:p[1]/w:r[3]/w:t')->nodeValue);
    }

    /**
     * Tests parsing of br.
     */
    public function testParseLineBreak(): void
    {
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        $html = '<p>This is some text<br/>with a linebreak.</p>';
        Html::addHtml($section, $html);

        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');

        self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:br'));
        self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:t'));
        self::assertEquals('This is some text', $doc->getElement('/w:document/w:body/w:p/w:r[1]/w:t')->nodeValue);
        self::assertEquals('with a linebreak.', $doc->getElement('/w:document/w:body/w:p/w:r[2]/w:t')->nodeValue);
    }

    /**
     * Test parsing of img.
     */
    public function testParseImage(): void
    {
        $src = __DIR__ . '/../_files/images/firefox.png';

        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        $html = '<p><img src="' . $src . '" width="150" height="200" style="float: right;"/><img src="' . $src . '" style="float: left;"/></p>';
        Html::addHtml($section, $html);

        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');

        $baseXpath = '/w:document/w:body/w:p/w:r';
        self::assertTrue($doc->elementExists($baseXpath . '/w:pict/v:shape'));
        self::assertStringMatchesFormat('%Swidth:150px%S', $doc->getElementAttribute($baseXpath . '[1]/w:pict/v:shape', 'style'));
        self::assertStringMatchesFormat('%Sheight:200px%S', $doc->getElementAttribute($baseXpath . '[1]/w:pict/v:shape', 'style'));
        self::assertStringMatchesFormat('%Smso-position-horizontal:right%S', $doc->getElementAttribute($baseXpath . '[1]/w:pict/v:shape', 'style'));
        self::assertStringMatchesFormat('%Smso-position-horizontal:left%S', $doc->getElementAttribute($baseXpath . '[2]/w:pict/v:shape', 'style'));
    }

    /**
     * Test parsing of img.
     */
    public function testParseImageSizeInPixels(): void
    {
        $src = __DIR__ . '/../_files/images/firefox.png';

        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        $html = '<p><img src="' . $src . '" width="150px" height="200px" /></p>';
        Html::addHtml($section, $html);

        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');

        $baseXpath = '/w:document/w:body/w:p/w:r';
        self::assertTrue($doc->elementExists($baseXpath . '/w:pict/v:shape'));
        self::assertStringMatchesFormat('%Swidth:150px%S', $doc->getElementAttribute($baseXpath . '[1]/w:pict/v:shape', 'style'));
        self::assertStringMatchesFormat('%Sheight:200px%S', $doc->getElementAttribute($baseXpath . '[1]/w:pict/v:shape', 'style'));
    }

    /**
     * Test parsing of img.
     */
    public function testParseImageSizeInPoints(): void
    {
        $src = __DIR__ . '/../_files/images/firefox.png';

        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        $html = '<p><img src="' . $src . '" width="150pt" height="200pt" /></p>';
        Html::addHtml($section, $html);

        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');

        $baseXpath = '/w:document/w:body/w:p/w:r';
        self::assertTrue($doc->elementExists($baseXpath . '/w:pict/v:shape'));
        self::assertStringMatchesFormat('%Swidth:200px%S', $doc->getElementAttribute($baseXpath . '[1]/w:pict/v:shape', 'style'));
        self::assertStringMatchesFormat('%Sheight:266.66666666667%S', $doc->getElementAttribute($baseXpath . '[1]/w:pict/v:shape', 'style'));
    }

    /**
     * Test parsing of img.
     */
    public function testParseImageSizeWithoutUnits(): void
    {
        $src = __DIR__ . '/../_files/images/firefox.png';

        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        $html = '<p><img src="' . $src . '" width="150" height="200" /></p>';
        Html::addHtml($section, $html);

        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');

        $baseXpath = '/w:document/w:body/w:p/w:r';
        self::assertTrue($doc->elementExists($baseXpath . '/w:pict/v:shape'));
        self::assertStringMatchesFormat('%Swidth:150px%S', $doc->getElementAttribute($baseXpath . '[1]/w:pict/v:shape', 'style'));
        self::assertStringMatchesFormat('%Sheight:200px%S', $doc->getElementAttribute($baseXpath . '[1]/w:pict/v:shape', 'style'));
    }

    /**
     * Test parsing of remote img.
     */
    public function testParseRemoteImage(): void
    {
        $src = self::getRemoteImageUrl();

        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        $html = '<p><img src="' . $src . '" width="150" height="200" style="float: right;"/><img src="' . $src . '" style="float: left;"/></p>';
        Html::addHtml($section, $html);

        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');

        $baseXpath = '/w:document/w:body/w:p/w:r';
        self::assertTrue($doc->elementExists($baseXpath . '/w:pict/v:shape'));
    }

    /**
     * Test parsing of remote img without extension.
     */
    public function testParseRemoteImageWithoutExtension(): void
    {
        $src = self::getRemoteImageUrlWithoutExtension();

        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        $html = '<p><img src="' . $src . '" width="150" height="200" style="float: right;"/><img src="' . $src . '" style="float: left;"/></p>';
        Html::addHtml($section, $html);

        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');

        $baseXpath = '/w:document/w:body/w:p/w:r';
        self::assertTrue($doc->elementExists($baseXpath . '/w:pict/v:shape'));
    }

    /**
     * Test parsing embedded image.
     */
    public function testParseEmbeddedImage(): void
    {
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        $html = '<p><img src="" name="Imagen 12" align="bottom" width="208" height="183" border="0"/></p>';
        Html::addHtml($section, $html);

        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');

        $baseXpath = '/w:document/w:body/w:p/w:r';
        self::assertTrue($doc->elementExists($baseXpath . '/w:pict/v:shape'));
    }

    /**
     * Test parsing of remote img that can be found locally.
     */
    public function testParseRemoteLocalImage(): void
    {
        $src = 'https://fakedomain.io/images/firefox.png';
        $localPath = __DIR__ . '/../_files/images/';
        $options = [
            'IMG_SRC_SEARCH' => 'https://fakedomain.io/images/',
            'IMG_SRC_REPLACE' => $localPath,
        ];

        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        $html = '<p><img src="' . $src . '" width="150" height="200" style="float: right;"/></p>';
        Html::addHtml($section, $html, false, true, $options);

        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');

        $baseXpath = '/w:document/w:body/w:p/w:r';
        self::assertTrue($doc->elementExists($baseXpath . '/w:pict/v:shape'));
    }

    /**
     * Test parsing of remote img that can be found locally.
     */
    public function testCouldNotLoadImage(): void
    {
        $this->expectException(Exception::class);
        $src = 'https://fakedomain.io/images/firefox.png';

        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        $html = '<p><img src="' . $src . '" width="150" height="200" style="float: right;"/></p>';
        Html::addHtml($section, $html, false, true);
    }

    public function testParseLink(): void
    {
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        $html = '<p><a href="https://phpoffice.github.io/PHPWord/" style="text-decoration: underline">link text</a></p>';
        Html::addHtml($section, $html);

        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');

        self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:hyperlink'));
        self::assertEquals('link text', $doc->getElement('/w:document/w:body/w:p/w:hyperlink/w:r/w:t')->nodeValue);
        self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:hyperlink/w:r/w:rPr/w:u'));
        self::assertEquals('single', $doc->getElementAttribute('/w:document/w:body/w:p/w:hyperlink/w:r/w:rPr/w:u', 'w:val'));
    }

    public function testParseLink2(): void
    {
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        $section->addBookmark('bookmark');
        $html = '<p><a href="#bookmark">internal link text</a></p>';
        Html::addHtml($section, $html);
        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');

        self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:hyperlink'));
        self::assertTrue($doc->getElement('/w:document/w:body/w:p/w:hyperlink')->hasAttribute('w:anchor'));
        self::assertEquals('bookmark', $doc->getElement('/w:document/w:body/w:p/w:hyperlink')->getAttribute('w:anchor'));
    }

    public function testParseLinkAllowsAbsenceOfHref(): void
    {
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        $html = '<p><a>text of href-less link</a></p>';
        Html::addHtml($section, $html);
        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');

        self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:hyperlink'));
        self::assertEquals('text of href-less link', $doc->getElement('/w:document/w:body/w:p/w:hyperlink/w:r/w:t')->nodeValue);

        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        $html = '<p><a href="">text of empty-href link</a></p>';
        Html::addHtml($section, $html);
        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');

        self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:hyperlink'));
        self::assertEquals('text of empty-href link', $doc->getElement('/w:document/w:body/w:p/w:hyperlink/w:r/w:t')->nodeValue);
    }

    public function testParseMalformedStyleIsIgnored(): void
    {
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        $html = '<p style="">text</p>';
        Html::addHtml($section, $html);
        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');
        self::assertFalse($doc->elementExists('/w:document/w:body/w:p[1]/w:pPr/w:jc'));
    }

    /**
     * Tests parsing hidden text.
     */
    public function testParseHiddenText(): void
    {
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        $html = '<p style="display: hidden">This is some hidden text.</p>';
        Html::addHtml($section, $html);

        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');

        self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:rPr/w:vanish'));
    }

    /**
     * Tests parsing letter spacing.
     */
    public function testParseLetterSpacing(): void
    {
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        $html = '<p style="letter-spacing: 150px">This is some text with letter spacing.</p>';
        Html::addHtml($section, $html);

        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');

        self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:rPr/w:spacing'));
        self::assertEquals(150 * 15, $doc->getElement('/w:document/w:body/w:p/w:r/w:rPr/w:spacing')->getAttribute('w:val'));
    }

    /**
     * Tests checkbox input field.
     */
    public function testInputCheckbox(): void
    {
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        $html = '<input type="checkbox" checked="true" /><input type="checkbox" />';
        Html::addHtml($section, $html);

        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');

        self::assertTrue($doc->elementExists('/w:document/w:body/w:p[1]/w:r/w:fldChar/w:ffData/w:checkBox'));
        self::assertEquals(1, $doc->getElement('/w:document/w:body/w:p[1]/w:r/w:fldChar/w:ffData/w:checkBox/w:checked')->getAttribute('w:val'));

        self::assertTrue($doc->elementExists('/w:document/w:body/w:p[2]/w:r/w:fldChar/w:ffData/w:checkBox'));
        self::assertEquals(0, $doc->getElement('/w:document/w:body/w:p[2]/w:r/w:fldChar/w:ffData/w:checkBox/w:checked')->getAttribute('w:val'));
    }

    /**
     * Parse horizontal rule.
     */
    public function testParseHorizontalRule(): void
    {
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();

        // borders & backgrounds are here just for better visual comparison
        $html = <<<HTML
<p>Simple default rule:</p>
<hr/>
<p>Custom style rule:</p>
<hr style="margin-top: 30px; margin-bottom: 0; border-bottom: 5px lightblue solid;" />
<p>END</p>
HTML;

        Html::addHtml($section, $html);
        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');

        // default rule
        $xpath = '/w:document/w:body/w:p[2]/w:pPr/w:pBdr/w:bottom';
        self::assertTrue($doc->elementExists($xpath));
        self::assertEquals('single', $doc->getElement($xpath)->getAttribute('w:val')); // solid
        self::assertEquals('1', $doc->getElement($xpath)->getAttribute('w:sz')); // 1 twip
        self::assertEquals('000000', $doc->getElement($xpath)->getAttribute('w:color')); // black

        // custom style rule
        $xpath = '/w:document/w:body/w:p[4]/w:pPr/w:pBdr/w:bottom';
        self::assertTrue($doc->elementExists($xpath));
        self::assertEquals('single', $doc->getElement($xpath)->getAttribute('w:val'));
        self::assertEquals((int) (5 * 15 / 2), $doc->getElement($xpath)->getAttribute('w:sz'));
        self::assertEquals('lightblue', $doc->getElement($xpath)->getAttribute('w:color'));

        $xpath = '/w:document/w:body/w:p[4]/w:pPr/w:spacing';
        self::assertTrue($doc->elementExists($xpath));
        self::assertEquals(450, $doc->getElement($xpath)->getAttribute('w:before'));
        self::assertEquals(0, $doc->getElement($xpath)->getAttribute('w:after'));
        self::assertEquals(240, $doc->getElement($xpath)->getAttribute('w:line'));
    }

    /**
     * Parse ordered list start & numbering style.
     */
    public function testParseOrderedList(): void
    {
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();

        // borders & backgrounds are here just for better visual comparison
        $html = <<<HTML
<ol>
    <li>standard ordered list line 1</li>
    <li>standard ordered list line 2</li>
</ol>

<ol start="5" type="A">
    <li>ordered list alphabetical, <span style="background-color: #EEEEEE; color: #FF0000;">line 5 => E</span></li>
    <li>ordered list alphabetical, <span style="background-color: #EEEEEE; color: #FF0000;">line 6 => F</span></li>
</ol>

<ol start="3" type="i">
    <li>ordered list roman lower, line <b>3 => iii</b></li>
    <li>ordered list roman lower, line <b>4 => iv</b></li>
</ol>

HTML;

        Html::addHtml($section, $html);
        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');

        // compare numbering file
        $xmlFile = 'word/numbering.xml';

        // default - decimal start = 1
        $xpath = '/w:numbering/w:abstractNum[1]/w:lvl[1]/w:start';
        self::assertTrue($doc->elementExists($xpath, $xmlFile));
        self::assertEquals('1', $doc->getElement($xpath, $xmlFile)->getAttribute('w:val'));

        $xpath = '/w:numbering/w:abstractNum[1]/w:lvl[1]/w:numFmt';
        self::assertTrue($doc->elementExists($xpath, $xmlFile));
        self::assertEquals('decimal', $doc->getElement($xpath, $xmlFile)->getAttribute('w:val'));

        // second list - start = 5, type A = upperLetter
        $xpath = '/w:numbering/w:abstractNum[2]/w:lvl[1]/w:start';
        self::assertTrue($doc->elementExists($xpath, $xmlFile));
        self::assertEquals('5', $doc->getElement($xpath, $xmlFile)->getAttribute('w:val'));

        $xpath = '/w:numbering/w:abstractNum[2]/w:lvl[1]/w:numFmt';
        self::assertTrue($doc->elementExists($xpath, $xmlFile));
        self::assertEquals('upperLetter', $doc->getElement($xpath, $xmlFile)->getAttribute('w:val'));

        // third list - start = 3, type i = lowerRoman
        $xpath = '/w:numbering/w:abstractNum[3]/w:lvl[1]/w:start';
        self::assertTrue($doc->elementExists($xpath, $xmlFile));
        self::assertEquals('3', $doc->getElement($xpath, $xmlFile)->getAttribute('w:val'));

        $xpath = '/w:numbering/w:abstractNum[3]/w:lvl[1]/w:numFmt';
        self::assertTrue($doc->elementExists($xpath, $xmlFile));
        self::assertEquals('lowerRoman', $doc->getElement($xpath, $xmlFile)->getAttribute('w:val'));
    }

    /**
     * Parse ordered list start & numbering style.
     */
    public function testParseVerticalAlign(): void
    {
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();

        // borders & backgrounds are here just for better visual comparison
        $html = <<<HTML
<table width="100%">
    <tr>
        <td width="20%" style="border: 1px #666666 solid;">default</td>
        <td width="20%" style="vertical-align: top; border: 1px #666666 solid;">top</td>
        <td width="20%" style="vertical-align: middle; border: 1px #666666 solid;">middle</td>
        <td width="20%" valign="bottom" style="border: 1px #666666 solid;">bottom</td>
        <td bgcolor="#DDDDDD"><br/><br/><br/><br/><br/><br/><br/></td>
    </tr>
</table>
HTML;

        Html::addHtml($section, $html);
        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');

        $xpath = '/w:document/w:body/w:tbl/w:tr/w:tc[1]/w:tcPr/w:vAlign';
        self::assertFalse($doc->elementExists($xpath));

        $xpath = '/w:document/w:body/w:tbl/w:tr/w:tc[2]/w:tcPr/w:vAlign';
        self::assertTrue($doc->elementExists($xpath));
        self::assertEquals('top', $doc->getElement($xpath)->getAttribute('w:val'));

        $xpath = '/w:document/w:body/w:tbl/w:tr/w:tc[3]/w:tcPr/w:vAlign';
        self::assertTrue($doc->elementExists($xpath));
        self::assertEquals('center', $doc->getElement($xpath)->getAttribute('w:val'));

        $xpath = '/w:document/w:body/w:tbl/w:tr/w:tc[4]/w:tcPr/w:vAlign';
        self::assertTrue($doc->elementExists($xpath));
        self::assertEquals('bottom', $doc->getElement($xpath)->getAttribute('w:val'));
    }

    /**
     * Fix bug - don't decode double quotes inside double quoted string.
     */
    public function testDontDecodeAlreadyEncodedDoubleQuotes(): void
    {
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();

        // borders & backgrounds are here just for better visual comparison
        $html = <<<HTML
<div style="font-family: Arial, &quot;Helvetice Neue&quot;">This would crash if inline quotes also decoded at loading XML into DOMDocument!</div>
HTML;

        Html::addHtml($section, $html);
        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');
        self::assertIsObject($doc);
    }

    public static function providerParseWidth(): array
    {
        return [
            ['auto', 5000, TblWidth::PERCENT],
            ['100%', 5000, TblWidth::PERCENT],
            ['200pt', 3999.999999999999, TblWidth::TWIP],
            ['300px', 4500, TblWidth::TWIP],
            ['400', 6000, TblWidth::TWIP],
        ];
    }

    /**
     * Test ruby.
     */
    public function testParseRubyHtml(): void
    {
        $html = <<<HTML
<ruby lang="en-US" style="line-height: 8pt;font-size:20pt;ruby-align:center;">
    base text
    <rp>(</rp>
    <rt style="line-height: 4pt;font-size:10pt">ruby text</rt>
    <rp>)</rp>
</ruby>
HTML;
        $phpWord = new PhpWord();
        $section = $phpWord->addSection();
        Html::addHtml($section, $html);

        $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');
        self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:ruby'));
        self::assertEquals('ruby text', $doc->getElement('/w:document/w:body/w:p/w:r/w:ruby/w:rt/w:r/w:t')->textContent);
        self::assertEquals(
            'base text',
            $doc->getElement('/w:document/w:body/w:p/w:r/w:ruby/w:rubyBase/w:r/w:t')->textContent
        );
        self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:ruby/w:rubyPr'));
        self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:ruby/w:rubyPr/w:rubyAlign'));
        self::assertEquals(
            RubyProperties::ALIGNMENT_CENTER,
            $doc->getElementAttribute('/w:document/w:body/w:p/w:r/w:ruby/w:rubyPr/w:rubyAlign', 'w:val')
        );
        self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:ruby/w:rubyPr/w:hps'));
        self::assertEquals(
            10,
            $doc->getElementAttribute('/w:document/w:body/w:p/w:r/w:ruby/w:rubyPr/w:hps', 'w:val')
        );
        self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:ruby/w:rubyPr/w:hpsBaseText'));
        self::assertEquals(
            20,
            $doc->getElementAttribute('/w:document/w:body/w:p/w:r/w:ruby/w:rubyPr/w:hpsBaseText', 'w:val')
        );
        self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:ruby/w:rubyPr/w:lid'));
        self::assertEquals(
            'en-US',
            $doc->getElementAttribute('/w:document/w:body/w:p/w:r/w:ruby/w:rubyPr/w:lid', 'w:val')
        );
    }
}
